/*
 *  Copyright 2004 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.webapp.taglib.core.html;

import com.blandware.atleap.webapp.taglib.core.BaseHandlerTag;
import com.blandware.atleap.webapp.taglib.core.ParamParent;
import com.blandware.atleap.webapp.util.core.RequestUtil;
import com.blandware.atleap.webapp.util.core.WebappUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.Globals;
import org.apache.struts.taglib.TagUtils;
import org.apache.struts.taglib.html.Constants;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.JspFragment;
import java.io.IOException;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>Generates an HTML hyperlink. Generation of HTML element is divided on
 * number of methods, which may be overridden in subclass to provide different
 * or additional functionality or even render another HTML element, which
 * behaviour is the same as behaviour of HTML Anchor.
 * </p>
 * <p>
 * If <b>name</b> is specified, than anchor with given name will be generated.
 * Else, link with rewritten URL will be produced. For definition of 'URL
 * rewriting', see {@link RewriteUrlTag}.
 * </p>
 * <p>
 * Allowed attributes are:
 * <ul>
 * <li>
 * <b>action</b> - name of Action to construct URL for
 * </li>
 * <li>
 * <b>forward</b> - name of Global Action forward to construct URL for
 * </li>
 * <li>
 * <b>href</b> - URL to rewrite
 * </li>
 * <li>
 * <b>name</b> - link name for named links
 * </li>
 * <li>
 * <b>anchor</b> - the anchor to be added to the end of the generated hyperlink
 * </li>
 * <li>
 * <b>target</b> - window target
 * </li>
 * <li>
 * <b>transaction</b> - whether to include transaction token (if any) in the
 * URL. May be "false" or "true", default is "false"
 * </li>
 * <li>
 * <b>addLocaleSuffix</b> - whether to add locale suffix before extension. May
 * be "false" or "true", default is "true".
 * </li>
 * </ul>
 * </p>
 * <p>
 * This tag may also accept parameters through &lt;atleap:param&gt; tags. They
 * are added to resulting URL.
 * </p>
 * <p>
 * Only one of <b>action</b>, <b>forward</b>, <b>name</b> or <b>href</b> must be
 * specified.
 * </p>
 * <p>
 * Here's an example:
 * <pre>
 * &lt;atleap:link action="login"&gt;
 *     &lt;atleap:param name="error" value="true"&gt;
 * &lt;/atleap:link&gt;
 * </pre>
 * This code will generate a link to page corresponding to 'login' action,
 * URL will contain a parameter with name 'error' and value 'true'.
 * </p>
 * <p><a href="LinkTag.java.html"><i>View Source</i></a></p>
 *
 * @author Sergey Zubtsovskiy <a href="mailto:sergey.zubtsovskiy@blandware.com">&lt;sergey.zubtsovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.9 $ $Date: 2006/08/03 10:08:23 $
 * @see com.blandware.atleap.webapp.taglib.core.ParamTag
 * @jsp.tag name="link"
 * body-content="scriptless"
 */
public class LinkTag extends BaseHandlerTag implements ParamParent {

	protected transient final Log log = LogFactory.getLog(getClass());

	/**
	 * Name of Action to construct URL for
	 */
	protected String action;

	/**
	 * Name of Global Action forward to construct URL for
	 */
	protected String forward;

	/**
	 * HREF to rewrite
	 */
	protected String href;

	/**
	 * The link name for named links.
	 */
	protected String name;

	/**
	 * The anchor to be added to the end of the generated hyperlink.
	 */
	protected String anchor;

	/**
	 * The window target.
	 */
	protected String target;

	/**
	 * Include transaction token (if any) in the hyperlink?
	 */
	protected Boolean transaction = Boolean.FALSE;

	/**
	 * Whether or not to add locale suffix before extension
	 */
	protected Boolean addLocaleSuffix = Boolean.TRUE;

	/**
	 * Local parameters
	 */
	protected Map parameters = new HashMap();

    /**
     * If <code>true</code>, no check is performed that action or forward or
     * href or name exist. In current tag this is always <code>false</code>,
     * but this can be modified in derived classes to prevent parameter
     * validation.
     */
    protected boolean suppressValidation = false;

	/**
     * Returns action
     *
	 * @return action
     * @see #action
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Name of Action to construct URL for"
	 */
	public String getAction() {
		return action;
	}

    /**
     * Sets action
     *
     * @param action action to set
     * @see #action
     */
	public void setAction(String action) {
		this.action = action;
	}

	/**
     * Returns forward
     *
     * @return forward
     * @see #forward
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Name of Global Action forward to construct URL for"
	 */
	public String getForward() {
		return forward;
	}

    /**
     * Sets forward
     *
     * @param forward forward to set
     * @see #forward
     */
	public void setForward(String forward) {
		this.forward = forward;
	}

	/**
     * Returns href
     *
     * @return href
     * @see #href
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="HREF to rewrite"
	 */
	public String getHref() {
		return href;
	}

    /**
     * Sets href
     *
     * @param href href to set
     * @see #href
     */
	public void setHref(String href) {
		this.href = href;
	}

	/**
     * Returns link name
     *
     * @return link name
     * @see #name
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Link name for named links"
	 */
	public String getName() {
		return name;
	}

    /**
     * Sets link name
     *
     * @param name link name to set
     * @see #name
     */
	public void setName(String name) {
		this.name = name;
	}

	/**
     * Returns anchor
     *
     * @return anchor
     * @see #anchor
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Anchor to append to the name of generated hyperlink"
	 */
	public String getAnchor() {
		return anchor;
	}

    /**
     * Sets anchor
     *
     * @param anchor anchor to set
     * @see #anchor
     */
	public void setAnchor(String anchor) {
		this.anchor = anchor;
	}

	/**
     * Returns window target
     *
     * @return window target
     * @see #target
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="The window target"
	 */
	public String getTarget() {
		return target;
	}

    /**
     * Sets window target
     *
     * @param target window target to set
     * @see #target
     */
	public void setTarget(String target) {
		this.target = target;
	}

	/**
     * Returns whether or not to include transaction token in the hyperlink
     *
     * @return whether to include transaction token in the hyperlink
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Boolean"
	 * description="Whether or not to include transaction token in the generated hyperlink"
	 */
	public Boolean getTransaction() {
		return transaction;
	}

    /**
     * Sets whether or not to include transaction token in the hyperlink
     *
     * @param transaction whether to include transaction token in the hyperlink
     */
	public void setTransaction(Boolean transaction) {
		this.transaction = transaction;
	}

	/**
     * Returns whether or not to add locale suffix before extension
     *
     * @return whether to add locale suffix before extension
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Boolean"
	 * description="Whether or not to add locale suffix before extension"
	 */
	public Boolean getAddLocaleSuffix() {
		return addLocaleSuffix;
	}

    /**
     * Sets whether or not to add locale suffix before extension
     *
     * @param addLocaleSuffix whether to add locale suffix before extension
     */
	public void setAddLocaleSuffix(Boolean addLocaleSuffix) {
		this.addLocaleSuffix = addLocaleSuffix;
	}

    /**
     * Processes the tag
     *
     * @throws JspException
     * @throws IOException
     */
	public void doTag() throws JspException, IOException {

		PageContext pageContext = (PageContext) getJspContext();
		HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
		HttpServletResponse response = (HttpServletResponse) pageContext.getResponse();

		// check for necessary attributes
        if (!suppressValidation) {
            if ( name == null && action == null && forward == null && href == null ) {
                JspTagException e = new JspTagException("Because 'name' attribute is not specified, either 'action' or 'forward' or 'href' attrbute must be specified");
                throw e;
            }
        }

		if ( transaction == null ) {
			transaction = Boolean.FALSE;
		}

		if ( addLocaleSuffix == null ) {
			addLocaleSuffix = Boolean.TRUE;
		}

		// invoke body content
		JspFragment body = getJspBody();
		String bodyContent = null;
		if ( body != null ) {
			StringWriter sw = new StringWriter();
			body.invoke(sw);
			bodyContent = sw.toString();
		}

		// calculate transaction token if requested
		if ( transaction.booleanValue() ) {
			HttpSession session = pageContext.getSession();
			String token = null;
			if ( session != null ) {
				token = (String) session.getAttribute(Globals.TRANSACTION_TOKEN_KEY);
			}

			if ( token != null ) {
				parameters.put(Constants.TOKEN_KEY, token);
			}
		}

		// calculate href
		String url = null;
		try {
			url = WebappUtil.computeURL(action, forward, href, anchor, parameters, request, response, true, false, addLocaleSuffix.booleanValue());
		} catch ( MalformedURLException e ) {
			throw new JspTagException(e);
		}

		StringBuffer result = new StringBuffer();
		openElement(result);
		appendLocation(result, url);
        appendAttributes(result);

        closeOpeningTag(result);

		// append body content and closing tag
		if ( bodyContent != null && bodyContent.trim().length() > 0 ) {
			appendBody(result, bodyContent);
		}
		closeElement(result);

		// write result to output
		TagUtils.getInstance().write(pageContext, result.toString());

	}

	/**
	 * Appends opening tag of HTML element, which renders a hyperlink, to the specified StringBuffer
     *
	 * @param element String buffer to append opening tag to
	 */
	protected void openElement(StringBuffer element) {
        element.append("<a ");
	}

	/**
	 * Appends location to the specified StringBuffer
     *
	 * @param element String buffer to append location to
	 * @param location
	 */
	protected void appendLocation(StringBuffer element, String location) {
		if ( name == null ) {
			element.append("href=\"").append(location).append("\" ");
		} else {
			element.append("name=\"").append(name).append("\" ");
		}
	}

	/**
	 * Appends the rest of attributes, including ones from BaseHandlerTag, to the specified StringBuffer
     * 
	 * @param element String buffer to append attributes to
	 */
	protected void appendAttributes(StringBuffer element) {
		if ( target != null ) {
			element.append("target=\"").append(target).append("\" ");
		}
		element.append(prepareAttributes());
	}

    /**
     * Appends HTML code that closes opening tag
     *
     * @param element String buffer to append to
     */
    protected void closeOpeningTag(StringBuffer element) {
        element.append(">");
    }

	/**
	 * Appends body content
	 * @param element String buffer to append body content to
	 * @param body Body content to append to buffer
	 */
	protected void appendBody(StringBuffer element, String body) {
        element.append(body);
	}

	/**
	 * Appends closing tag to the specified StringBuffer
	 * @param element String buffer to append closing tag to
	 */
	protected void closeElement(StringBuffer element) {
        element.append("</a>");
	}

	/**
	 * @see com.blandware.atleap.webapp.taglib.core.ParamParent#addParameter(String, Object)
	 */
	public void addParameter(String name, Object value) {
		parameters.put(name, RequestUtil.mergeValues(parameters.get(name), value));
	}

}
